//Copyright 2007 Kwikproject. All rights reserved.
//
//Redistribution and use in source and binary forms, with or without modification, 
//are permitted provided that the following conditions are met:
//
//Redistributions of source code must retain the above copyright notice, 
//this list of conditions and the following disclaimer.
//
//Redistributions in binary form must reproduce the above copyright notice, 
//this list of conditions and the following disclaimer in the documentation and/or 
//other materials provided with the distribution.
//
//THIS SOFTWARE IS PROVIDED BY KWIKPROJECT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
//INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
//FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KWIKPROJECT OR CONTRIBUTORS 
//BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
//DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
//THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//The views and conclusions contained in the software and documentation are those of the 
//authors and should not be interpreted as representing official policies, either 
//expressed or implied, of Kwikproject. 

/** Kwik base package @singleton */
Kwik = {__package:1};

/** Kwik data package @singleton */
Kwik.Data = {__package:1};

/** Kwik DOM package @singleton */
Kwik.Dom = {__package:1};

/** Checksum package @singleton */
Kwik.Checksum = {__package:1};

/** Collection package @singleton */
Kwik.Collections = {__package:1};

/** Drawing package @singleton */
Kwik.Drawing = {__package:1};

/** Encryption package @singleton */
Kwik.Encryption = {__package:1};

/** Network related object package @singleton */
Kwik.Net = {__package:1};

/** Ui base package @singleton */
Kwik.Ui = {__package:1};

/** Ui animations package @singleton */
Kwik.Ui.Animations = {__package:1};

/** Ui controls package @singleton */
Kwik.Ui.Controls = {__package:1};

/** Ui controls package @singleton */
Kwik.Ui.Devices = {__package:1};

/** Ui element specific package @singleton */
Kwik.Ui.Xhtml = {__package:1};

/**
* Kwik version
* @singleton
* @created 1/16/2009
* @modified 1/16/2009
*/
Kwik.Version = 
{
	/** @ignore */
	__class: ["Kwik.Version"],
	/** Major version number, 0 @type Number */
	major: 0,
	/** Minor version number, 0 @type Number */
	minor: 0,
	/** Revision number, 11 @type Number */
	revision: 11
};


/**
* Object registery maintains references to Kwik.Object types using pointers which refer to a location in the object array.
* This supplies the base functionality of global access for Kwik.Object types.
* @singleton
* @created 1/16/2009
* @modified 1/16/2009
*/
Kwik.ObjectRegistry = 
{
	/** List of all registered object @type Kwik.Object[] */
	objects: [],
	/** Add an object to the registry, and returns the pointer @type Number */
	add: function(obj) 
	{
		if(Kwik.ObjectRegistry.objects.length == 0)
		{
			Kwik.Utility.attachEvent(window, "onunload", Kwik.ObjectRegistry.dispose);
		}
		return Kwik.ObjectRegistry.objects.push(obj) - 1;
	},

	/** Remove an object from the registry  */
	remove: function(obj) 
	{
		try { return Kwik.ObjectRegistry.objects.splice(obj.ptr, 1)[0];}
		catch (x) { }
		return null;
	},
	
	/** Clear the object array and dispose all objects, attached to a window.onunload event */
	dispose: function()
	{
		while(Kwik.ObjectRegistry.objects.length>0)
		{
			var obj = Kwik.ObjectRegistry.objects.splice(0,1)[0];
			if(obj && obj.dispose){obj.dispose();}
		}
	}
};

/**
* Base object, supplies event stacking and registration
* @constructor
* @created 1/16/2009
* @modified 1/16/2009
*/
Kwik.Object = function() 
{
	/** Class identifier list @type string[] */
	this.__class = ["Kwik.Object"];

	/** Incremental pointer to this object @type Number */
	this.ptr = Kwik.ObjectRegistry.add(this);

	/** Global reference string @type String */
	this.ref = "Kwik.ObjectRegistry.objects[" + this.ptr.toString() + "]";

	/** Global reference string @type String */
	this.referenceString = function() { return "Kwik.ObjectRegistry.objects[" + this.ptr.toString() + "]"; };

	/** Event by name collection, store the stack of Function objects by event name @type Function[] */
	this.events = {};

	/** Add a handler to an event by name */
	this.attachEvent = function(name, handler) 
	{
		if (!Kwik.Type.isFunction(handler)) { throw new Error("Kwik.Object.attachEvent: Invalid parameter specified, expected: Function or Delegate"); }
		if (!this.events[name] || !this.events[name].pop) { this.events[name] = []; }
		this.events[name].push(handler);
	};

	/** Insert a handler into the stack by next index or handler */
	this.insertEvent = function(name, handler, next) 
	{
		if (!Kwik.Type.isFunction(handler)) { throw new Error("Kwik.Object.insertEvent: Invalid parameter specified, expected: Function or Delegate"); }
		if (!this.events[name] || !this.events[name].pop) { this.events[name] = []; }
		var index = this.getEventIndex(name, next);
		if (index > -1) 
		{
			var arr = this.events[name].splice(index, this.events[name].length - index);
			this.events[name] = this.events[name].concat(handler, arr);
			return;
		}
		this.events[name].push(handler);
	};

	/** Remove an event from the stack by index or handler @type Function */
	this.detachEvent = function(name, handler) 
	{
		var index = this.getEventIndex(name, handler);
		while (index > -1) 
		{
			this.events[name].splice(index, 1)[0];
			index = this.getEventIndex(name, handler);
		}
		return null;
	};

	/** Drop the entire stack of functions by event name @type Function[] */
	this.clearEvent = function(name) 
	{
		if (this.events[name] && this.events[name].pop) { return this.events[name].splice(0, this.events[name].length); }
		return [];
	};

	/** Execute all functions by event name and returns the last result @type Object*/
	this.fireEvent = function(name, args) 
	{
		if(!this.events[name])
		{
			//Kwik.Page.log.write("Kwik.Object.fireEvent: Event '" + name + "' does not exist.");
			return;
		}

		args = args || [];
		this.onbeforeevent(name, args);
		var ret = null;
		
		if(args && !args.push)
		{
			throw new Error("Kwik.Object.fireEvent: Invalid parameter specified for 'args', Array expected.\r\n events['" + name + "][" + i.toString() + "]"); 
		}
		
		var arr = this.events[name];
		var len = arr.length;
		for (var i = 0; i < len; i++) 
		{
			ret = arr[i].apply(this, args);
		}
		this.onevent(name, args);
		return ret;
	};

	/** Get an event index by name and handler @type Number */
	this.getEventIndex = function(name, handler) 
	{
		if (!this.events[name] || !this.events[name].pop) { return -1; }

		var arr = this.events[name];
		var len = arr.length;

		for (var i=0; i < len; i++) 
			if (arr[i] == handler)
				return i;

		return -1;
	};

	/** Get the type name of the class @type String */
	this.getType = function()
	{
		return this.__class[this.__class.length-1];
	};

	/** 
	* Configure this object with the supplied parameter.
	* Evaluates the parameter which can be of type object or string and if undefined an empty object is initialized.
	* When a string is specified it will be evaluated to object
	* Finally it scans for event declarations starting with 'on', if found and the event is declared as a function member it will be attached.
	*/
	this.configure = function Object_configure(config)
	{
		if(typeof config == "undefined" || config == null)
		{
			config = {};
		}
		if(Kwik.Type.isString(config))
		{
			if(config.charCodeAt(0) != 123){config = ["{", config, "}"].join("");}
			try
			{
				eval("config = " + config);
			}
			catch(x)
			{
				throw new Error("Kwik.Object.configure: Failed to evaluate config string to object.\r\n" + x.description);
			}
		}

		//scanning for events
		for(m in config)
		{
			if(config[m] == Object.prototype[m]){continue;}
			if(m.substr(0,2) === "on")
			{
				var fn = config[m];
				if(Kwik.Type.isString(fn))
				{
					try
					{
						fn = new Function(fn);
					}
					catch(x)
					{
						throw new Error("Kwik.Object.configure: Failed to initialize function, reason: " + x.description);
					}
				}
				if(Kwik.Type.isFunction(fn))
				{
					this.attachEvent(m, fn);
					Kwik.Page.log.write("Kwik.Object.configure: Handler for event '" + m + "' added.");
				}
			}
		}

		this.fireEvent("onconfigure", [config]);
		return config;
	};
	
	/** Event fired when the object is configured */
	this.onconfigure = function(config){};
	/** Event fired before the object fires an event */
	this.onbeforeevent = function(eventName, args){};
	/** Event fired when the object fires an event */
	this.onevent = function(eventName, args){};
};

/**
* Delegated task executes a function in a certain context with optional parameters.
* To delay execution specify a delay, for immidiate execution an autoexec can be defined constructinf the object.
* @extends Kwik.Object
* @requires Kwik.Object
* @constructor
* @created 3:30 AM 4/6/2008
* @modified 3:30 AM 4/6/2008
*/
Kwik.Delegate = function(config)
{
	/** @ignore */
	Kwik.Utility.extend(this, "Kwik.Object");
	this.__class.push("Kwik.Delegate");

	/** Task to executed @type Function */
	this.task = null;
	/** Context object to which the task applies @type Object */
	this.context = null;
	/** Arguments for this task @type Object[] */
	this.arguments = [];
	/** Time in milliseconds to delay a task @type Number */
	this.delay = 0;
	/** Internal pointer to a timeout @type Object */
	this._timeout = null;

	/** Execute the task */
	this.execute = function()
	{
		this.fireEvent("onbeforeexecute");
		if(this._timeout!=null){window.clearTimeout(this._timeout); this._timeout = null;}
		this._timeout = setTimeout("var obj=" + this.referenceString() + "; obj.task.apply(obj.context, obj.arguments);", this.delay);
		this.fireEvent("onexecute");
	};

	/** Cancel the task */
	this.abort = function()
	{
		this.fireEvent("onbeforeabort");
		if(this._timeout!=null){window.clearTimeout(this._timeout); this._timeout = null;}
		this.fireEvent("onabort");
	};

	this.configure = function Delegate_configure(config)
	{

		config = this.Kwik$Object.configure(config);

		if(Kwik.Type.isFunction(config.task)){this.task = config.task;}
		if(Kwik.Type.isObject(config.context)){this.context = config.context;}
		if(Kwik.Type.isArray(config.arguments)){this.arguments = config.arguments;}
		if(Kwik.Type.isNumber(config.delay)){this.delay = config.delay;}
		if(config.autoexec === true){this.execute();}

		return config;	
	};

	/** Event fired before execute */
	this.onbeforeexecute = function(){};
	/** Event fired on execute */
	this.onexecute = function(){};
	/** Event fired before cancel */
	this.onbeforecancel = function(){};
	/** Event fired on cancel */
	this.oncancel = function(){};

	/** @ignore */
	this.main = function Delegate_main(config)
	{
		if(config){this.configure(config);}
	}; this.main(config);
};
/**
* Logger object which gathers log lines
* @extends Kwik.Object
* @constructor
* @created 1/16/2009
* @modified 1/16/2009
*/
Kwik.Logger = function() 
{
	/** @ignore */
	Kwik.Utility.extend(this, "Kwik.Object");
	this.__class.push("Kwik.Logger");

	/** Indicator whether the log is enabled, logWrite() does not add lines until this is set to true @type Boolean */
	this.enabled = false;
	/** Internal log array to wich lines are added @type String[] */
	this.lines = [];
	/** Write an item to the log, time information will be added. */
	this.write = function Logger_write(line) 
	{
		if (!this.enabled) { return; }
		this.lines.push(this._prefix() + " " + line);
	};
	/** Log line prefix, a time indicaton hh:mm:ss.fff @type String*/
	this._prefix = function Logger__prefix() 
	{
		var dt = new Date();
		var ret = [dt.getHours().toString(), dt.getMinutes().toString(), dt.getSeconds().toString()];
		for(e in ret){ret[e] = (ret[e].length==1? "0": "") + ret[e];}
		var ms = (dt.getMilliseconds()).toString();
		while(ms.length<3){ms = "0" + ms;}
		return ret.join(":") + "." + ms;
	};
	/** Clear the log */
	this.clear = function Logger_clear() 
	{
		this.lines.splice(0, this.log.length);
	};
	/** Joins the lines to a string @type String */
	this.toString = function Logger_toString() 
	{
		return this.lines.join("\r\n");
	}
};
/**
* Logger which logs to a HTMLElement
* @extends Kwik.Logger
* @constructor
* @created 1/16/2009
* @modified 1/16/2009
*/
Kwik.PageLogger = function() 
{
	/** @ignore */
	Kwik.Utility.extend(this, "Kwik.Logger");
	this.__class.push("Kwik.PageLogger");

	/** Array of lines written before the document.body was ready @type String[] */
	this._defered = [];
	/** A HTMLDiv to which lines are written @type HTMLDiv */
	this._logElement = null;
	this.isReady = false;
	/** Write a log line to the log element */
	this.write = function PageLogger_write(line) 
	{
		if(this.enabled !== true) { return; }
		if(!this.isReady){this._defered.push(line); return;}
		if(this._logElement == null)
		{
			this._logElement = document.getElementById("PageLogger");
			if(this._logElement == null)
			{
				this._logElement = document.body.appendChild(document.createElement("div"));
			}
			this._logElement.className = "Kwik-PageLogger";
		}

		var div = document.createElement("div");
		div.className = this._logElement.childNodes.length % 2 == 0? "Even": "Odd";
		this._logElement.insertBefore(div, this._logElement.firstChild).innerHTML = this._prefix() + " " + line;

	};

	/** Clear all lines from the log element */
	this.clear = function PageLogger_clear() 
	{
		while (this._logElement.childNodes.length > 0) 
		{
			this._logElement.removeChild(this._logElement.childNodes[0]);
		}
	};

	/** Writes the buffered lines after document.body ready */
	this._windowLoad = function PageLogger__windowLoad()
	{
		Kwik.Page.log.write("Kwik.PageLogger._writeDefered: Writing buffered lines.");
		this.isReady = true;

		for(e in this._defered){this.write(this._defered[e]);}
	};

	/** @ignore */
	this.main = function PageLogger_main()
	{
		Kwik.Utility.attachEvent(window, "onload", new Function(this.referenceString() + "._windowLoad()"));
	}; this.main();
};

/**
* XmlHttpRequest wrapper for asynchronous request
* @extends Kwik.Object
* @constructor
* @created 1/16/2009
* @modified 1/16/2009
*/
Kwik.AsyncRequest = function(config)
{
	/** @ignore */
	Kwik.Utility.extend(this, "Kwik.Object");
	this.__class.push("Kwik.AsyncRequest");

	/** Internal XMLHttpRequest object @type XMLHttpRequest */
	this.xhr = Kwik.Utility.getXhr();
	/** URL used to get the information from @type String */
	this.url = "";
	/** HTTP request method @type String */
	this.method = "GET";
	/** Authentication name @type String */
	this.name = null;
	/** Authentication password @type String */
	this.password = null;
	/** State of the object, copy of the XMLHttpRequest.readyState property @type Number */
	this.state = 0;
	/** HTTP status code of the response @type Number */
	this.reponseStatus = 0;
	/** XMLDocument if the response contains XML @type XMLDocument */
	this.responseXML = null;
	/** Text from the response @type String */
	this.responseText = "";
	/** Data which is send using the send method, can be String or IStream types @type Object */
	this.requestData = null;
	/** List of request headers by name @type Object */
	this.requestHeaders = {};
	/** List of response headers by name @type Object */
	this.responseHeaders = {};

	/** Open the request */
	this.open = function(url, method, headers, name, password)
	{
		this.url = url || this.url;
		this.method = method || this.method;
		this.name = name || null;
		this.password = password || null;

		var cancel = this.fireEvent("onbeforeopen", [this.url]);
		if(cancel === true){return;}

		if(typeof headers == "object")
			for(e in headers)
				if(headers[e] != Object.prototype[e])
					this.requestHeaders[e] = headers[e];

		this.xhr.open(this.method, this.url, true, this.name, this.password);

		for(e in this.requestHeaders)
		{
			if(this.requestHeaders[e] == Object.prototype[e]){continue;}
			this.xhr.setRequestHeader(e, this.requestHeaders[e]);
		}
		this.fireEvent("onopen", [this.url]);
	};

	/** Send the data to specified resource */
	this.send = function(data)
	{
		data = data || this.requestData|| null;
		this.fireEvent("onbeforesend", [data]);
		this.xhr.onreadystatechange = new Function(this.referenceString() + "._statechange()");
		this.requestData = data;
		this.xhr.send(this.requestData);
		this.fireEvent("onsend", [data]);
	};

	/** Abort current request */
	this.abort = function()
	{
		this.xhr.onreadystatechange = function(){};
		this.xhr.abort();
		this.state = 0;
		this.fireEvent("onabort");
	};

	/** Internal method which tracks state changes and fires the 'oncomplete' event */
	this._statechange = function()
	{
		this.state = this.xhr.readyState;
		if(this.xhr.readyState == 4)
		{
			var hdrs = this.xhr.getAllResponseHeaders();
			var list = hdrs.split("\n");
			for(var i=0; i<list.length; i++)
			{
				var val = list[i].replace(/^\s+|\s+$/g,"");
				if(val.indexOf(":")!=-1)
				{
					var name = val.substr(0, val.indexOf(":")).replace(/^\s+|\s+$/g,"");
					var value = val.substr(val.indexOf(":")+1).replace(/^\s+|\s+$/g,"");
					this.responseHeaders[name] = value;
				}
			}
			this.responseXML = this.xhr.responseXML;
			this.responseText = this.xhr.responseText;
			this.responseStatus = this.xhr.status;
			this.xhr.onreadystatechange = function(){};
			this.fireEvent("oncomplete");
		}
		this.fireEvent("onstatechange", [this.state]);
	};

	/** Configure this async request @type Object */
	this.configure = function AsyncRequest_configure(config)
	{
		config = this.Kwik$Object.configure(config);
		if(config.url){this.url = config.url;}
		if(config.method){this.method = config.method;}
		if(config.data){config.requesData = config.data;}
		if(config.requesData){this.requestData = config.requestData;}
		if(config.headers){config.requestHeaders = config.headers;}
		if(config.requestHeaders)
		{
			for(e in config.requestHeaders)
				if(config.requestHeaders[e] != Object.prototype[e])
					this.requestHeaders[e] = config.requestHeaders[e];
		}
		if(config.autoexec === true){this.open(); this.send();}

		return config;
	};

	/** Event fired before the opening the request */
	this.onbeforeopen = function(url){};
	/** Event fired when opening the request */
	this.onopen = function(url){};
	/** Event fired before sending the data */
	this.onbeforesend = function(data){};
	/** Event fired after sending the data */
	this.onsend = function(data){};
	/** Event fired when request is complete */
	this.oncomplete = function(){};
	/** Event fired when request is aborted */
	this.onabort = function(){};
	/** Event fired when the state changes */
	this.onstatechange = function(state){};

	/** @ignore */
	this.main = function AsyncRequest_Main(config)
	{
		if(config){this.configure(config);}
	}; this.main(config);
};

/**
* Loader for compiling scripts
* @extends Kwik.Object
* @constructor
* @created 1/16/2009
* @modified 1/16/2009
*/
Kwik.ScriptLoader = function(dependencyFilter)
{
	/** @ignore */
	Kwik.Utility.extend(this, "Kwik.Object");
	this.__class.push("Kwik.ScriptLoader");

	/** Initial script name used for setting dependency path @type String */
	this.bootScript = "Kwik.js";
	/** Path to dependency files @type String */
	this.path = "";
	/** Script loading or loaded with this loader @type Object */
	this.scripts = {};
	/** Filter to match dependency inclusion @type RegExp */
	this.dependencyFilter = dependencyFilter || /^[\w{1,}\.]{1,}include\("([\w\.]{1,})"\);$/igm;

	/** Method for including scripts, the first call will set the path and adds current scripts from the HTML head @type Kwik.Script */
	this.include = function(scriptName)
	{
		if(scriptName == null || scriptName == ""){return null;}
		if(scriptName.toLowerCase() in this.scripts)
		{
			Kwik.Page.log.write("Kwik.ScriptLoader.include: Script '" + scriptName + "' in collection exiting.");
			return this.scripts[scriptName.toLowerCase()];
		}

		if(this.path.length == 0)
		{
			var list = document.documentElement.getElementsByTagName("script");
			for(var i=0, len=list.length; i<len; i++)
			{
				if(list[i].src.length != 0)
				{
					Kwik.Page.log.write("Kwik.ScriptLoader.include: Script added from HTMLHead '" + name + "'");
					var name = list[i].src.split("/").pop();
					if(!name.toLowerCase() in this.scripts)
					{
						this.scripts[name.toLowerCase()] = new Kwik.Script({name: name, loader:loader, isLoaded:true});
						this.scripts[name.toLowerCase()].text = list[i].text;
					}
					if(name.toLowerCase() == this.bootScript.toLowerCase())
					{
						this.path = list[i].src.substr(0, list[i].src.length - this.bootScript.length);
						Kwik.Page.log.write("Kwik.ScriptLoader.include: Path set to '" + this.path + "'");
					}
				}
			}
		}

		Kwik.Page.log.write("Kwik.ScriptLoader.include: New script created for loading '" + scriptName + "'");
		var script = new Kwik.Script({name:scriptName, loader:this});

		script.attachEvent("oncomplete", new Function(this.referenceString()+ ".fireEvent(\"onscriptcomplete\", [this]);"));
		script.open(this.path + scriptName);
		script.send();
		this.scripts[scriptName.toLowerCase()] = script;
		
		return script;
	};

	/** Get the list of script objects @type Kwik.Script[] */
	this.getList = function()
	{
		var arr = [];
		for(e in this.scripts){this.scripts[e]._appended=false;}
		for(e in this.scripts){arr = arr.concat(this._getScriptRecursive(this.scripts[e]));}
		return arr;
	};

	/** Get the full script loaded with this loader @type String */
	this.getScript = function()
	{
		var arr = this.getList();
		for(e in arr){arr[e]=arr[e].text;}
		return arr.join(";\r\n");
	};

	/** Internal method to align scripts and dependencies @type Kwik.Script[] */
	this._getScriptRecursive = function(script)
	{
		var arr = [];
		if(script._appended){return [];}
		script._appended = true;

		for(var i=0; i<script.dependencies.length; i++)
			if(!script.dependencies[i]._appended)
				arr = arr.concat(this._getScriptRecursive(script.dependencies[i]));

		arr.push(script);
		return arr;
	}

	/** Event handler fired after a script complete event. */
	this._scriptComplete = function(script)
	{
		if(this._timerComplete){window.clearTimeout(this._timerComplete);}
		this._timerComplete = window.setTimeout(this.referenceString() + "._checkComplete();", 150);
	};
	
	/** Internal method which checks script whether they are downloaded or not, if all scripts are ready it will fire the oncomplete event for this object */
	this._checkComplete = function()
	{
		for(e in this.scripts)
		{
			if(this.scripts[e] == Object.prototype[e]){continue;}
			if(!this.scripts[e].isLoaded)
			{
				Kwik.Page.log.write("Kwik.ScriptLoader._checkComplete: Waiting for '" + e + "'  to load, escaping oncomplete");
				return;
			}
		}
		Kwik.Page.log.write("Kwik.ScriptLoader._checkComplete: All scripts loaded invoking oncomplete");
	
		this.fireEvent("oncomplete");	
	};
	
	/** Event fired when a script doenload completes */
	this.onscriptcomplete = function(){};
	/** Event fired when all scripts are downloaded */
	this.oncomplete = function(){};

	/** @ignore */
	this.main = function()
	{
		this.attachEvent("onscriptcomplete", this._scriptComplete);
	}; this.main();
};

/**
* Extends Kwik.AsyncRequest and is used for loading scripts asynchronously.
* After receiving the script it will remove the comments and filter out the the dependencies.
* @extends Kwik.AsyncRequest
* @requires Kwik.AsyncRequest
* @constructor
* @created 1/16/2009
* @modified 1/16/2009
*/
Kwik.Script = function(config)
{
	/** @ignore */
	Kwik.Utility.extend(this, "Kwik.AsyncRequest");
	this.__class.push("Kwik.Script");

	/** Array of scripts on which this script depends @type Kwik.Script[] */
	this.dependencies = [];
	/** Name of the script file @type String */
	this.name = "";
	/** Script text, without references and comments @type String */
	this.text = "";
	/** Indication whether the script is loaded or not @type Boolean */
	this.isLoaded = false;
	/** Reference to the Kwik.ScriptLoader object which created this object @type Kwik.ScriptLoader */
	this.loader = null;

	/** Event handler for the 'oncomplete', removes comments and ads dependencies @type Function */
	this._completed = function Script__completed()
	{
		this.isLoaded = true;
		this.text = this.responseText;
		//this.text = this.text.replace(/\/\*[^*]*\*+([^\/][^*]*\*+)*\//ig,"");

		var arr = this.text.match(this.loader.dependencyFilter);
		this.text = this.text.replace(this.loader.dependencyFilter,"");

		Kwik.Page.log.write("Kwik.Script._oncomplete: Loading of '" + this.name + "' completed, status=" + this.responseStatus.toString());

		if(arr == null){return;}

		for(var i=0; i<arr.length; i++)
		{
			arr[i] = arr[i].replace(this.loader.dependencyFilter, "$1");
			var obj = this.loader.include(arr[i]);
			if(obj != null){this.dependencies.push(obj);}
		}
		Kwik.Page.log.write("Kwik.Script._oncomplete: Dependencies for '"+this.name+"' ('" + arr.join("','") + "') added.");
	};

	/** Configure this script @type Object */
	this.configure = function Script_configure(config)
	{
		config = this.Kwik$AsyncRequest.configure(config);
		if(config.name){this.name = config.name;}
		if(config.loader){this.loader = config.loader;}
		if(config.loaded){config.isLoaded = config.loaded;}
		if(config.isLoaded === true || config.isLoaded === false){this.isLoaded = config.isLoaded;}
		return config;
	};

	/** @ignore */
	this.main = function Script_main(config)
	{
		if(config){this.configure(config);}
		this.attachEvent("oncomplete", this._completed);
		this.requestHeaders["Accept"] = "application/x-javascript";
	}; this.main(config);
};

/**
* Type evaluation object
* @singleton
* @created 1/16/2009
* @modified 1/16/2009
*/
Kwik.Type =
{
	/** @ignore */
	__class: ["Kwik.Type"],
	/** Tests the parameter for a Function type @type Boolean */
	isFunction: function(test){return test instanceof Function;},
	/** Tests the parameter for a Date type @type Boolean */
	isDate: function(test){return test instanceof Date;},
	/** Tests the parameter for a Number type @type Boolean */
	isNumber: function(test){return typeof test == "number";},
	/** Tests the parameter for a Array type @type Boolean */
	isArray: function(test){return test instanceof Array;},
	/** Tests the parameter for a String type @type Boolean */
	isString: function(test){return typeof test == "string";},
	/** Tests the parameter for a Object type @type Boolean */
	isObject: function(test){return typeof test == "object";},
	/** Tests the parameter for a Error type @type Boolean */
	isError: function(test){return test instanceof Error;},
	/** Tests the parameter for a null value @type Boolean */
	isNull: function(test){return test==null;},
	/** Tests the parameter for Boolean type @type Boolean */
	isBoolean: function(test){return typeof test == "boolean";},
	/** Tests the parameter for a HtmlElement type @type Boolean */
	isElement: function(test){return test && test.nodeType==1;},
	/** Tests the parameter for 'undefined' type @type Boolean */
	isUndefined: function(test){return (typeof test == "undefined");},
	/** Tests the parameter for a undefined value @type Boolean */
	isDefinedType: function(typeName)
	{
		try
		{
			return eval("(typeof " + typeName + " != \"undefined\")");
		}
		catch(x)
		{
			return false;
		}
	},
	/** Tests the parameter for a Kwik type, determined by __class @type Boolean */
	isKwikType: function(test, typeName)
	{
		if(test && Kwik.Type.isArray(test.__class))
		{
			for(i=test.__class.length; i>=0;i--)
				if(test.__class[i] == typeName)
					return true;
		}
		return false;
	}
};

/**
* Type conversion 
* @singleton
* @created 1/16/2009
* @modified 1/16/2009
*/
Kwik.Convert = 
{
	/** @ignore */
	__class: ["Kwik.Convert"],
	/** Convert the supplied parameter to a String type @type String */
	toString: function(val)
	{
		if(val.toString){return val.toString();}
		return new String(val);
	},
	/** Convert the supplied parameter to a Number type using parseInt @type Number */
	toInt: function(val)
	{
		var ret = 0;
		try{ret=parseInt(val);}catch(e){ret=0;}
		if(isNaN(ret)){return 0;}
		return ret;
	},
	/** Convert the supplied parameter to a Number type using parseFloat @type Number */
	toFloat: function(val)
	{
		var ret = 0;
		try{ret=parseFloat(val);}catch(e){ret=0;}
		if(isNaN(ret)){return 0;}
		return ret;
	},
	/** Convert the supplied parameter to a Number type using parseFloat @type Number */
	toNumber: function(val)
	{
		return Kwik.Convert.toFloat(val);
	},
	/** Convert the supplied parameter to a Date type @type Date */
	toDate: function(val)
	{
		return new Date(val);
	}
};
/**
* Global utility which supplies object extension and crossbrowser features 
* @singleton
* @created 1/16/2009
* @modified 4:17 PM 6/25/2009
*/
Kwik.Utility = 
{
	/** Build a namespace */
	createNamespace: function(namespace)
	{
		var names = namespace.split(".");
		var current = window;
		for(var i=0; i<names.length; i++)
		{
			if(typeof current[names[i]] != "object"){current[names[i]] = {};}
			current = current[names[i]];
		}
	},
	/** Add the functions from the base class to the supplied object */
	extend: function(newObject, baseString)
	{
		if(!baseString){return;}

		var baseName = baseString.replace(/\./g, "$");
		newObject[baseName] = newObject.__$base = {};
		newObject[baseName].__$derived = newObject;
		
		try
		{
			eval("newObject.__$base.__$inherit = " + baseString + ";");
		}
		catch(x)
		{
			throw new Error("Kwik.Utility.extend: Failed to initialize the base class '" + baseString + "'.\r\n" + x.description);
		}
		newObject.__$base.__$inherit();
		for(e in newObject.__$base)
		{
			if(e != baseName && e.substr(0, 3) != "__$")
			{
				newObject[e] = newObject.__$base[e];
				if(newObject.__$base[e] && newObject.__$base[e].apply)
				{
					newObject.__$base["__$" + e] = newObject[e];
					newObject.__$base[e] = new Function(
						"var args = [];"
						+ "for(var i=0; i<arguments.length; i++){args.push(arguments[i]);}"
						+ "var ret = this[\"__$" + e + "\"].apply(this.__$derived, args);"
						+ "if(ret){return ret;}"
					);
				}
			}
		}
		
		var current = newObject.__$base;

		while(current)
		{
			current.__$derived = newObject;
			delete current.__$inherit;
			current = current.__$base;
		}
		newObject.ptr = Kwik.ObjectRegistry.add(newObject);
	},
	/** Get a XMLHttpRequest for various browsers @type XMLHttpRequest */
	getXhr: function() 
	{
		if(Kwik.BrowserInfo.internetExplorer)
		{
			var progids = ["MsXml2.XmlHttp.6.0", "MsXml2.XmlHttp.4.0", "MsXml2.XmlHttp.3.0", "Microsoft.XmlHttp"];
			for(var i=0; i<progids.length; i++)
			{
				try{obj = new ActiveXObject(progids[i]); return obj;}
				catch(x){}
			}
		}
		else
		{
			return new XMLHttpRequest();
		}
	},
	/** Get a XMLDomDocument for various browsers @type XMLDomDocument */
	getDom: function(xml)
	{
		if (xml && typeof DOMParser != "undefined") { return DOMParser.parseFromString(xml, "text/xml"); }
		if (Kwik.BrowserInfo.internetExplorer) 
		{
			var progids = ["MsXml2.DomDocument.6.0", "MsXml2.DomDocument.4.0", "MsXml2.DomDocument.3.0", "Microsoft.XmlDom"];
			for (var i = 0; i < progids.length; i++) 
			{
				try{var obj = new ActiveXObject(progids[i]);}
				catch (x) { }
			}
			
			if (obj && xml) 
			{ 
				try{obj.loadXML(xml);}
				catch (x){this.log.write("Kwik.Utility.getDom: Failed to load xml.\r\n" + obj.parseError.reason);}
			}
			return obj;
		}
		else
		{
			return document.implementation.createDocument("", "", null);
		}
	},

	/** Attach an event handler to an object, returns true on success @type Boolean */
	attachEvent: function(obj, evt, handler)
	{
		if(typeof handler.apply == "undefined"){throw new Error("Kwik.Utility.attachEvent: Incorrect parameter 'handler'");}
		if(typeof evt.indexOf == "undefined"){throw new Error("Kwik.Utility.attachEvent: Incorrect parameter 'evt'");}

		if(typeof obj.attachEvent != "undefined")
		{
			obj.attachEvent(evt, handler);
			return true;
		}

		if(typeof obj.addEventListener != "undefined")
		{
			obj.addEventListener(evt.substr(2), handler, false); 
			return true;
		}
		return false;
	},
	/** Remove an event handler from an object, returns true on success @type Boolean */
	detachEvent: function(obj, evt, handler)
	{
		if(typeof handler.apply == "undefined"){throw new Error("Kwik.Utility.attachEvent: Incorrect parameter 'handler'");}
		if(typeof evt.indexOf == "undefined"){throw new Error("Kwik.Utility.attachEvent: Incorrect parameter 'evt'");}

		if(typeof obj.detachEvent != "undefined")
		{
			obj.detachEvent(evt, handler);
			return true;
		}
		if(typeof obj.removeEventListener != "undefined")
		{
			obj.removeEventListener(evt.substr(2), handler, false); 
			return true;
		}
		return false;
	}
};

/**
* Browser info object
* @singleton
* @created 1/16/2009
* @modified 11:55 AM 4/6/2009
*/
Kwik.BrowserInfo = 
{
	/** @ignore */
	__class: ["Kwik.BrowserInfo"],

	/** Browser supports ActiveX @type Boolean */
	activeXControls   : navigator.appName.toLowerCase().indexOf("microsoft")!=-1,
	/** Browser supports background sounds (not implemented) @type Boolean */
	backgroundSounds  : false,
	/** Browser is a Beta version @type Boolean */
	beta              : navigator.appName.toLowerCase().indexOf("beta")!=-1,
	/** Browser name @type String */
	browser           : navigator.userAgent,
	/** Browser supports CDF (not implemented) @type Boolean */
	CDF               : false,
	/** Browser's current charset @type String */
	charset           : document.defaultCharset,
	/** Browser's CLR version (not implemented) @type Object */
	clrVersion        : null,
	/** Browser's compatibillity mode @type String */
	compatMode        : document.compatMode,
	/** Browser operates in strict mode @type Boolean */
	//strictMode        : document.compatMode.toLowerCase().indexOf("backward")==-1,
	/** Browser support cookies @type Boolean */
	cookies           : navigator.cookieEnabled,
	/** Platform CPU class @type String */
	cpu               : navigator.cpuClass,
	/** Browser is a crawler @type String */
	crawler           : navigator.appName.toLowerCase().indexOf("crawler")!=-1 || navigator.appName.toLowerCase().indexOf("bot")!=-1,
	/** Browser's ecmascript version (not implemented) @type Number */
	ecmaScriptVersion : 0,

	/** Browser supports frames @type Boolean */
	frames            : window.frames!=undefined,
	/** Browser supports java applets @type Boolean */
	javaApplets       : navigator.javaEnabled(),
	/** Browser language @type String */
	language          : navigator.userLanguage,
	/** Browser major version @type Number */
	majorVersion      : navigator.appVersion,
	/** Browser minor version @type Number */
	minorVersion      : navigator.appMinorVersion,
	/** Browser MS XML DOM version (not implemented) @type Number */
	msDomVersion      : 0,
	/** Operating system @type String */
	platform          : navigator.platform,
	/** Browser supports HTMLTable types (not implemented) @type Boolean */
	tables            : true,
	/** Browser supports VBScript (not implemented) @type Boolean */
	vbScript          : false,
	/** Browser W3C DOM version (not implemented) @type Number */
	W3CDomVersion     : 0,
	/** Browser platform is windows 16-bit @type Boolean */
	win16             : navigator.platform == "Win16",
	/** Browser platform is windows 32-bit @type Boolean */
	win32             : navigator.platform == "Win32",

	/** Browser is AOL @type Boolean */
	aol               : navigator.appName.indexOf("AOL")!=-1,
	/** Browser is Gecko based @type Boolean */
	gecko           : navigator.userAgent.indexOf("Gecko") != -1,
	/** Browser is Firefox @type Boolean */
	firefox           : navigator.userAgent.indexOf("Firefox") != -1,
	/** Browser is MS Intrnet Explorer @type Boolean */
	internetExplorer  : navigator.userAgent.indexOf("MSIE") != -1,
	/** Browser is Konqeror @type Boolean */
	konqueror         : navigator.vendor && navigator.vendor.indexOf("KDE") != -1,
	/** Browser is NS Navigator @type Boolean */
	netscapeNavigator : navigator.userAgent.indexOf("Netscape") != -1,
	safari: navigator.vendor && navigator.vendor.indexOf("Apple") != -1,
	chrome: navigator.userAgent.indexOf("Chrome") != -1,
	omniweb: navigator.userAgent.indexOf("Omniweb") != -1,
	opera: window.opera && true
};

/**
* Page object of which the onload function should be overridden to use it's functionality when ready.
* @requires Kwik.PageLogger
* @requires Kwik.ScriptLoader
* @singleton
* @created 1/16/2009
* @modified 1/16/2009
*/
Kwik.Page = 
{
	/** Indication whether the loader completed loading of scripts */
	_isLoaderComplete: false,
	/** Indication whether the window.document is ready */
	_isWindowComplete: false,
	/** List of scripts loaded by the loader @type Kwik.Script[] */
	_scriptList: [],
	/** List of script URL's which where loaded @type String[] */
	_scriptListLoaded: [],
	/** Page logger used for debugging @type Kwik.PageLogger */
	log: new Kwik.PageLogger(),
	/** Reference to the HTMLHead element @type HTMLElement */
	head: null,
	/** Reference to the HTMLTitle element @type HTMLElement */
	title: null,
	/** Reference to the HTMLBody element @type HTMLElement */
	body: null,
	/** Script loader for Kwik.Page.include option @type Kwik.ScriptLoader */
	scriptLoader: new Kwik.ScriptLoader(/^Kwik\.Page\.include\("([\w\.]{1,})"\);$/igm),

	/** Include a script */
	include: function(scriptName)
	{
		if(this._loaderTimer){clearTimeout(this._loaderTimer);}
		Kwik.Page.scriptLoader.include(scriptName);
	},
	/** Triggered when all loading has completed */
	onload: function(){},

	/** Internal method which set environment and invokes the onload function */
	_pageLoad: function()
	{
		Kwik.Page.log.write("Kwik.Page._pageLoad: Page initialization starting.");


		Kwik.Page._scriptList = Kwik.Page.scriptLoader.getList();
		if(Kwik.Page._scriptList.length>0)
		{
			for(e in Kwik.Page._scriptList)
			{
				var script = this.head.appendChild(document.createElement("script"));
				script.type = "text/javascript";
				if(Kwik.Page.debug)
				{
					script.onload = function(){ Kwik.Page._scriptLoad(this)};
					script.src = Kwik.Page._scriptList[e].url;
				}
				else
				{
					script.text = Kwik.Page._scriptList[e].text;
				}
			}
		}

		if(!Kwik.Page.debug)
		{
			Kwik.Page.log.write("Kwik.Page._pageLoad: Page initialization complete, invoking onload().");
			Kwik.Page.onload();
		}
	},

	/** Event handler for script.onload while debugging */
	_scriptLoad: function(script)
	{
		Kwik.Page.log.write("Kwik.Page._scriptLoad: Loading script by source complete, " + script.src);
		Kwik.Page._scriptListLoaded.push(script.src);
		if(Kwik.Page._scriptListLoaded.length == Kwik.Page._scriptList.length)
		{
			Kwik.Page.log.write("Kwik.Page._scriptLoad: Scripts loaded, invoking onload().");
			Kwik.Page.onload();
		}
	},

	/** Event handler for loader.onscriptcomplete */
	_scriptComplete: function()
	{
		if(Kwik.Page._loaderTimer)
		{
			window.clearTimeout(Kwik.Page._loaderTimer);
			Kwik.Page.log.write("Kwik.Page._scriptComplete: Loader idle time tester cleared.");
		}
		Kwik.Page.scriptLoader.detachEvent("onscriptcomplete", Kwik.Page._scriptComplete);
	},

	/** Event handler for loader.oncomplete */
	_loaderComplete: function()
	{
		Kwik.Page.log.write("Kwik.Page._loaderComplete: Script loader completed.");
		Kwik.Page._isLoaderComplete = true;
		if(Kwik.Page._isWindowComplete){Kwik.Page._pageLoad();}
	},

	/** Event handler for window.onload */
	_windowComplete: function()
	{
		Kwik.Page.log.write("Kwik.Page._windowComplete: Window object loaded.");
		Kwik.Page._isWindowComplete = true;
		Kwik.Page.head = document.getElementsByTagName("head")[0];
		Kwik.Page.title = Kwik.Page.head.getElementsByTagName("title")[0];
		Kwik.Page.body = document.getElementsByTagName("body")[0];
		if(Kwik.Page._isLoaderComplete){Kwik.Page._pageLoad();}
	},
	
	/** Creates a textarea with the current loaded script */
	writeFullScript: function()
	{
		if(!Kwik.Page._fullScriptArea)
		{
			Kwik.Page._fullScriptArea = document.createElement("textarea");
			Kwik.Page._fullScriptArea.style.cssText = "width: 100%; height:200px;";
			document.body.appendChild(Kwik.Page._fullScriptArea);
		}

		Kwik.Page._fullScriptArea.value = this.scriptLoader.getScript();
	},

	/** @ignore */
	init: function()
	{
		Kwik.Page.scriptLoader.attachEvent("oncomplete", Kwik.Page._loaderComplete);
		Kwik.Page.scriptLoader.attachEvent("onscriptcomplete", Kwik.Page._scriptComplete);
		Kwik.Utility.attachEvent(window, "onload", Kwik.Page._windowComplete);
		
		//Kwik.Utility.attachEvent(window, "onerror", new Function("Kwik.Page.log.write(\"ERROR\");"));
		Kwik.Page._loaderTimer = window.setTimeout("Kwik.Page._loaderComplete()", 500);
	}
};
Kwik.Page.init();